﻿using System;
using OrganizationName.Factory;
using System.IO;
using System.Threading;
using OrganizationName.CodeGeneration;
using System.Collections.Generic;
using System.Reflection;
using OrganizationName.Common;

namespace OrganizationName.Factory
{
    public class DynamicFactory : IConfiguredThroughXml, IDisposable
    {
        private DynamicFactory _RealFactory;
        private string _SourceXmlFile;
        private string _SourceXslFile;
        private FileSystemWatcher _XmlFileModificationMonitor;
        private FileSystemWatcher _XslFileModificationMonitor;
        private bool _IsDisposed;
        private bool _IsRecompileInProgress;
        private DateTime _SourceXmlFileLastModifiedDate = DateTime.MinValue;
        private DateTime _SourceXslFileLastModifiedDate = DateTime.MinValue;

        internal const string FACTORY_GET_METHOD_NAME = "Get";
        internal const string FACTORY_EXISTS_METHOD_NAME = "Exists";
        internal const string FACTORY_SET_METHOD_NAME = "Set";

        internal const string FACTORY_METHOD_PARAMETER_SERVICE_TYPE = "serviceType";
        internal const string FACTORY_METHOD_PARAMETER_SERVICE_NAME = "serviceName";
        internal const string FACTORY_METHOD_PARAMETER_EXPECTED_TYPE = "expectedType";

        internal const string DYNAMIC_FACTORY_IMPLEMENTATION_TYPE_NAME = "DynamicFactoryImpl";

        public DynamicFactory()
        {
        }

        public virtual void Configure(string xmlFilePath)
        {
            Configure(xmlFilePath, false);
        }

        public virtual void Configure(string xmlFilePath, bool monitorFiles)
        {
            RuntimeConfiguration runtimeConfiguration = new RuntimeConfiguration();
            runtimeConfiguration.Configure(xmlFilePath);

            _RealFactory = CompileRealFactory(runtimeConfiguration);

            _SourceXmlFile = xmlFilePath;

            if (monitorFiles)
            {
                MonitorSourceFiles();
            }
        }

        public virtual void Configure(string xmlFilePath, string xslFilePath)
        {
            Configure(xmlFilePath, xslFilePath, false);
        }

        public virtual void Configure(string xmlFilePath, string xslFilePath, bool monitorFiles)
        {
            RuntimeConfiguration runtimeConfiguration = new RuntimeConfiguration();
            runtimeConfiguration.Configure(xmlFilePath, xslFilePath);

            _RealFactory = CompileRealFactory(runtimeConfiguration);

            _SourceXmlFile = xmlFilePath;
            _SourceXslFile = xslFilePath;

            if (monitorFiles)
            {
                MonitorSourceFiles();
            }
        }

        public virtual void Configure(Stream xmlStream)
        {
            RuntimeConfiguration runtimeConfiguration = new RuntimeConfiguration();
            runtimeConfiguration.Configure(xmlStream);

            _RealFactory = CompileRealFactory(runtimeConfiguration);
        }

        private static DynamicFactory CompileRealFactory(RuntimeConfiguration runtimeConfiguration)
        {
            ICodeContext codeContext = new CodeContext(runtimeConfiguration, DYNAMIC_FACTORY_IMPLEMENTATION_TYPE_NAME);
            codeContext.Class.BaseTypes.Add(typeof(DynamicFactory));

            runtimeConfiguration.GenerateCode(codeContext);

            Assembly compiledAssembly = codeContext.Compile();
            DynamicFactory realFactory = Activator.CreateInstance(compiledAssembly.GetTypes()[0]) as DynamicFactory;
            return realFactory;
        }

        private void MonitorSourceFiles()
        {
            if (_SourceXmlFile != null)
            {
                _SourceXmlFileLastModifiedDate = File.GetLastWriteTime(_SourceXmlFile);
                _XmlFileModificationMonitor = Utility.MonitorFileForModification(_SourceXmlFile, OnSourceFileModified);
            }
            if (_SourceXslFile != null)
            {
                _SourceXslFileLastModifiedDate = File.GetLastWriteTime(_SourceXslFile);
                _XslFileModificationMonitor = Utility.MonitorFileForModification(_SourceXslFile, OnSourceFileModified);
            }
        }

        private void ThrowIfDisposed()
        {
            if (_IsDisposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }

        private bool IsDirty()
        {
            DateTime sourceXmlFileLastModifiedDate = _SourceXmlFile != null ? File.GetLastWriteTime(_SourceXmlFile) : DateTime.MinValue;
            DateTime sourceXslFileLastModifiedDate = _SourceXslFile != null ? File.GetLastWriteTime(_SourceXslFile) : DateTime.MinValue;
            return _SourceXmlFileLastModifiedDate != sourceXmlFileLastModifiedDate || _SourceXslFileLastModifiedDate != sourceXslFileLastModifiedDate;
        }

        private void OnSourceFileModified(object sender, FileSystemEventArgs e)
        {
            if (!_IsRecompileInProgress)
            {
                lock (this)
                {
                    if (!_IsRecompileInProgress)
                    {
                        try
                        {
                            _IsRecompileInProgress = true;
                            Thread.Sleep(1000);
                            RuntimeConfiguration runtimeConfiguration = new RuntimeConfiguration();
                            if (IsDirty())
                            {
                                if (_SourceXslFile != null && _SourceXmlFile != null)
                                {
                                    runtimeConfiguration.Configure(_SourceXmlFile, _SourceXmlFile);
                                }
                                else if (_SourceXmlFile != null)
                                {
                                    runtimeConfiguration.Configure(_SourceXmlFile);
                                }
                                else
                                {
                                    return;
                                }
                            }
                            else
                            {
                                return;
                            }

                            _RealFactory = CompileRealFactory(runtimeConfiguration);


                            if (_SourceXmlFile != null)
                            {
                                _SourceXmlFileLastModifiedDate = File.GetLastWriteTime(_SourceXmlFile);
                            }
                            if (_SourceXslFile != null)
                            {
                                _SourceXslFileLastModifiedDate = File.GetLastWriteTime(_SourceXslFile);
                            }
                        }
                        catch (Exception factoryCompilationException)
                        {
                            // TODO: Logging & Notification
                        }
                        finally
                        {
                            _IsRecompileInProgress = false;
                        }
                    }
                }
            }
        }

        public void Dispose()
        {
            _IsDisposed = true;

            DisposeFileModificationMonitor(_XmlFileModificationMonitor);
            _XmlFileModificationMonitor = null;

            DisposeFileModificationMonitor(_XslFileModificationMonitor);
            _XslFileModificationMonitor = null;
        }

        private void DisposeFileModificationMonitor(FileSystemWatcher fileModificationMonitor)
        {
            if (fileModificationMonitor != null)
            {
                fileModificationMonitor.EnableRaisingEvents = false;
                fileModificationMonitor.Dispose();
            }
        }

        public T Get<T>()
        {
            ThrowIfDisposed();
            return (T)Get(typeof(T));
        }

        public T Get<T>(string name)
        {
            ThrowIfDisposed();
            return (T)Get(name);
        }

        public virtual object Get(System.Type serviceType)
        {
            ThrowIfDisposed();
            return _RealFactory.Get(serviceType);
        }

        public virtual object Get(string serviceName)
        {
            ThrowIfDisposed();
            return _RealFactory.Get(serviceName);
        }

        public virtual bool Exists(System.Type serviceType, System.Type expectedType)
        {
            ThrowIfDisposed();
            return _RealFactory.Exists(serviceType, expectedType);
        }

        public virtual bool Exists(string serviceName, System.Type expectedType)
        {
            ThrowIfDisposed();
            return _RealFactory.Exists(serviceName, expectedType);
        }
    }
}
